Java基础(七)——Java集合

Java集合类可用于存储数量不等的对象,并可以实现常用的数据结构,保存具有映射关系的数据。集合是用于存储其它对象的对象。
集合类只能存放对象,因此基础数据类型需要自动装箱成包装类才能存放。
(1)Set:无序、不可重复的集合。HashSet, TreeSet
(2)List:有序、可重复的集合。ArrayList, LinkedList, Vector
(3)Queue:队列集合。ArrayDeque, LinkedList, Deque(ArrayDeque)
(4)Map:具有映射关系的集合。HashMap, TreeMap, HashTable
Collection集合体系的继承树
Map体系的继承树
三种集合示意图

Collection和Iterator接口

集合元素本身(是一个对象),即集合元素存的对象(对象的真实值)
注意:使用Iterator和foreach()方法遍历的不是集合元素本身,而是集合元素真实值(即集合存的对象)。
注意:Collection接口继承了Iterable接口:

1
public interface Collection<E> extends Iterable<E>{ …}

而Iterable接口中封装了Iterator接口,因此只要实现了Iterable接口,就可以使用Iterator:

1
2
3
public interface Iterable<T>{
Iterator<T> iterator();
}

使用Lambda表达式遍历集合

使用的是Iterator接口的forEach()方法,传入的形参是Lambda表达式。forEach()方法遍历的是集合元素。

1
2
3
4
5
6
7
8
9
10
11
12
import java.util.Collection;
import java.util.HashSet;
public class CollectionEach{
public static void main(String[] args){
Collection books = new HashSet();
books.add("a");
books.add("b");
books.add("c");
//调用Iterable接口的forEach()方法遍历集合
books.forEach(obj -> System.out.println("迭代元素集合:" + obj));
}
}

其中的obj就是集合中的元素。

使用Iterator接口遍历集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class IteratorTest{
public static void main(String[] args){
Collection books = new HashSet();
books.add("a");
books.add("b");
books.add("c");
//获取books集合对应的迭代器
Iterator it = books.iterator();
while(it.hasNext()){
//it.next()返回的是Object类型,需要强制转换
String book = (String)it.next();
System.out.println(book);
if(book.equals("a")){
//从集合中删除上一次next()方法返回的元素
it.remove();
}
//对book变量赋值不会改变集合本身
book = "d";
}
System.out.println(books);
}
}

当使用Iterator对集合元素进行迭代时,Iterator并不是把集合本身传给了迭代变量,而是把集合元素的值传给了迭代变量,所以修改迭代变量的值对集合本身没有任何影响。
使用Iterator迭代过程中,不可修改集合元素。

使用Lambda表达式遍历Iterator

Iterator接口的forEachRemaining(Consumer action)方法,该方法的Consumer参数同样是函数式接口。

1
2
3
4
5
6
7
8
9
10
11
12
public class IteratorEach{
public static void main(String[] args){
Collection books = new HashSet();
books.add("a");
books.add("b");
books.add("c");
//获取books集合对应的迭代器
Iterator it = books.iterator();
//使用Lamda表达式(目标类型是Consumer)来遍历集合元素
it.forEachRemaining(obj -> System.out.println("books集合的元素:" + obj));
}
}

使用foreach循环遍历集合

foreach循环就是格式:for(类型 变量 : 集合或数组)。foreach起到替代迭代器的作用,从语法上来讲,数组或实现类Iterable接口的类实例,都可以使用foreach循环。而大多数集合类都实现了Iterable接口,因此可使用foreach循环。

1
2
3
4
for (Object obj : books){     
String b = (String) obj;
System.out.println(b);
}

1
2
3
4
for (String str : books){
Sysetem.out.prinltn(str);
System.out.println(b);
}

与使用Iterator接口遍历集合元素类似的是,使用foreach循环的迭代变量也不是集合元素本身,而是集合元素的真实值。因此为了避免共享资源引发的潜在问题,不能修改集合元素的真实值,否则会引发ConcurrentModificationException异常。

使用for循环遍历集合

for就是经典循环格式:for(类型 i=0; i < length; i++)

1
2
3
4
for (Iterator<String> iter = books.iterator(); iter.hasNext(); iter ++){
String book = iter.next();
System.out.println(book);
}

使用Predicate操作集合,它是函数式接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/*
*Predicate统计出现“疯狂”字符串的图书数量
*统计出现“Java”字符串的图书数量
*统计书名长度大于10的图书数量
*/
package com.Licht._08;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.function.Predicate;

public class PredicateTest2{
public static void main(String[] args){
//创建集合
Collection books = new HashSet();
books.add(new String("轻量级Java EE企业应用实战"));
books.add(new String("疯狂Java讲义"));
books.add(new String("疯狂iOS讲义"));
books.add(new String("疯狂Ajax讲义"));
books.add(new String("疯狂Android讲义"));
//传入三个Lambda表达式,其目标类型都是Predicate
System.out.println(calAll(books, ele -> ((String)ele).contains("疯狂")));
System.out.println(calAll(books, ele -> ((String)ele).contains("Java")));
System.out.println(calAll(books, ele -> ((String)ele).length() > 10));
}
//calAll()方法,使用Predicate判断每个集合元素是否满足特定条件
public static int calAll(Collection books, Predicate p){
int total = 0;
for (Object obj : books){
//使用Predicate的test()方法判断该对象是否满足Predicate指定条件
if(p.test(obj)){
total++;
}
}
return total;
}
}

先额外定义一个calAll()方法遍历集合元素,然后依次对每个集合进行判断。

Stream、IntStream、LongStream、DoubleStream

它们都是流式API,代表多个支持串行和并行聚集操作的元素。
利用builder()类方法创建对应的Builer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.Licht._08;
import java.util.stream.IntStream;

/*
*流式API的聚集方法,每次只能执行一次
*/
public class IntStreamTest{
public static void main(String [] args){
//利用类方法builer()创建对应的Builder
IntStream is = IntStream.builder()
.add(20)
.add(13)
.add(-2)
.add(18)
.build(); //调用Builder的build()方法获取对应的Stream
//下面调用聚集方法的代码每次只能执行一次
//System.out.println("is流中所有元素的最大值:" + is.max().getAsInt());
//System.out.println("is流中所有元素的最小值:" + is.min().getAsInt());
//System.out.println("is流中所有元素的h和:" + is.sum());
//System.out.println("is流中所有元素的总数:" + is.count());
//System.out.println("is流中所有元素的平均值:" + is.average());
//System.out.println("is流中所有元素的平方是否大于20:"
// + is.allMatch(ele -> ele * ele > 20));
//System.out.println("is流中是否包含任意元素的平方大于20:"
// + is.anyMatch(ele -> ele * ele > 20));
//将Stream映射成一个新Stream
IntStream newIs = is.map(ele -> ele *2 + 1);
//使用方法引用 来遍历集合元素
newIs.forEach(System.out::println);
}
}

Stream中的方法分:中间(intermediate)方法和末端(terminal)方法。
中间方法:中间操作允许流保持打开状态,并允许直接调用后续方法。方法的返回值是另一个方法。上面程序的map()方法就是中间方法。
末端方法:是对流的最终操作。当对某个Stream执行末端方法后,该流将会被“消耗”且不再可用。上面程序的sum()、count()、average()等方法就是末端方法。
Java8允许使用流式API来操作集合,Collection接口提供了一个stream()默认方法,可返回该集合对应的流,然后通过流式API来操作集合。
5中的PredicateTest2先额外定义一个calAll()方法遍历集合元素,然后依次对每个集合进行判断。这太麻烦了,可利用Stream直接对集合中的所有元素进行批量操作。

Set集合

Set集合与Collection类似,只是不允许有重复元素。
若试图将两个相同元素添加进Set集合中,则add()方法返回false,且新元素不会被加入。
Set集合的三个实现类:HashSet、TreeSet、EnumSet.

HashSet类

大多数时候使用Set集合就是使用HashSet类。HashSet使用Hash算法存储集合中的元素,因此具有良好的存取和查找功能。
特点:不能保证元素的排列顺序;HashSet不是同步的;集合元素值可能是null。
Hash集合判断两个元素相等的标准是,两个对象通过equal()方法比较相等,且两个对象通过hashCode()方法返回值相等。若相等则只会添加一个元素。
重写hashCode()方法的一般步骤:
(1)把对象内每个有意义的实例变量(即参与equals()方法比较的实例变量)计算出一个int类型的hashCode值;

hashCode值的计算方式

(2)用(1)中计算出的多个hashCode值组合计算出一个hashCode值返回。
当程序把可变对象加入到HashSet集合中后,尽量不要去修改该集合元素中参与计算hashCode()、equals()方法的实例变量。因为有可能导致修改的对象与原集合中的其它元素相等,从而导致HashSet无法准确访问该对象。

LinkedHashSet类

是HashSet的子类,它也是根据元素的hashCode值来决定元素的存储位置,同时使用链表维护元素的次序。即遍历LinkedHashSet类集合中的元素时,将会按元素的添加顺序来访问集合里的元素。

TreeSet类

是SortedSet接口的实现类。
对于TreeSet类的方法,其返回的元素是根据元素值的实际大小进行排序,而不是元素在原TreeSet类中的插入顺序
HashSet使用hash算法求得的hashCode值来决定元素的存储位置,而TreeSet使用红黑树的数据结构来存储集合元素。
TreeSet支持的两种排序:1.自然排序,即升序排序;2.定制排序,可降序等。
如果试图把一个对象添加到TreeSet类型集合时,除了第一个元素不需要实现Comparable接口,后面添加的所有对象的类必须实现Comparable接口(因为要进行比较嘛,以重写compareTo()方法),否则程序将会抛出异常。而且需要将比较对象obj强制转换成相同的类型。
如果希望TreeSet能正常运作,只能添加同一类型的对象。
TreeSet判断两个对象是否相等的唯一标准是,两个对象通过compareTo(Object obj)方法比较是否返回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Z implements Comparable{
int age;
public Z(int age){
this.age = age;
}
//重写equals()方法,总是返回true
public boolean equals(Object obj){
return true;
}
public int compareTo(Object obj){
return 1;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* 如果试图将一个对象添加到TreeSet时,该对象的类Z必须实现Comparable接口
*/
package com.Licht._08.TreeSetTest2;

import java.util.TreeSet;

public class TreeSetTest2{
public static void main(String[] args){
TreeSet set = new TreeSet();
Z z1 = new Z(6);
set.add(z1);
System.out.println(set.add(z1)); //第二次添加同一对象输出true,表明添加成功
//输出set集合,有两个元素
System.out.println(set);
//修改set集合的第一个元素的age变量,
//将看到第二个元素的age变量也变了
((Z)set.first()).age = 9; //集合的元素强制转换成Z类型,事项Comparable接口
System.out.println(((Z)set.last()).age);
}
}

TreeSet以及Z对象在内存中的示意图

上面程序set集合输出的两个元素很奇怪?因为集合的元素总是引用,但习惯上把引用的对象称为集合元素。
如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变对象的实例变量,这将导致它与其他对象的大小顺序发生变化,但TreeSet不会再次调整他们的顺序,甚至可能导致TreeSet中保存的两个对象通过compareTo(Object obj)方法比较返回0。如下面程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/*
* R是可变类,可通过改变R对象count实例变量的值,
* 改变集合中元素的count实例变量值
*/
package com.Licht._08.TreeSetTest3;
class R implements Comparable{
int count;
public R(int count){
this.count = count;
}
public String toString(){
return "R[count" + count + "]";
}
//重写equals()方法,根据count来判断是否相等
public boolean equals(Object obj){
if(this == obj){
return true;
}
if(obj != null && obj.getClass() == R.class){
R r = (R)obj;
return r.count == this.count;
}
return false;
}
//重写compareTo()方法,根据count来比较大小
public int compareTo(Object obj){
R r = (R)obj;
return count > r.count ? 1 :
count < r.count ? -1 : 0;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*
*如果向TreeSet中添加一个可变对象后,并且后面程序修改了该可变对象的实例变量,
*这将导致它与其他对象的大小顺序发生变化,但TreeSet不会再次调整他们的顺序,
*甚至可能导致TreeSet中保存的两个对象通过compareTo(Object obj)方法比较返回0.
*/
public class TreeSetTest3{
public static void main(String[] args){
TreeSet ts = new TreeSet();
ts.add(new R(5));
ts.add(new R(-3));
ts.add(new R(9));
ts.add(new R(-2));
System.out.println(ts); //TreeSet集合是有序排列
//取出第一个和最后一个元素,并赋值
R first = (R)ts.first(); //取值
first.count = 20; //count是在赋值
R last = (R)ts.last();
last.count = -2; //与原集合的最后一个元素相同
//再次输出TreeSet将发现元素处于无序状态,且有重复元素
System.out.println(ts);
//删除实例变量改变的量,删除失败
System.out.println(ts.remove(new R(-2))); //输出false
System.out.println(ts);
//删除实例变量未改变的量,删除成功
System.out.println(ts.remove(new R(5))); //输出true
System.out.println(ts);
}
}

上面程序当执行了代码System.out.println(ts.remove(new R(5))); //输出true后,TreeSet会对集合中的所有元素进行重新索引(不是重新排序),接下来就可以删除TreeSet 中的元素了(包括那些被修改过实例变量的元素)。
与HashSet类类似,TreeSet类若包含可变对象,修改可变对象的实例变量将很容易出错,建议不要修改放入HashSet和TreeSet集合中元素的关键实例变量

EnumSet类

是专为枚举类设计的集合类。
EnumSet类没有暴露任何构造器来创建实例,应该使用它提供的类方法来创建实例。
可以复制一个EnumSet集合或Collection集合中的所有元素来创建新的EnumSet集合,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum Seanson {
SPRING,SUMMER,FALL,WINTER
}
import java.util.Collection;
import java.util.HashSet;
import java.util.EnumSet;

public class EnumSetTest2{
public static void main(String[] args){
Collection c = new HashSet();
c.clear();
c.add(Seanson.FALL); //从枚举类Seanson中取元素添加
c.add(Seanson.SPRING);
EnumSet enumSet = EnumSet.copyOf(c);
System.out.println(enumSet);
}
}

各Set类的性能分析

Set类的三个实现类HashSet、TreeSet、EnumSet。只有当需要一个保持排序的Set时,才应该是用TreeSet,否则使用HashSet(因为常用的添加、查询元素等操作HashSet比TreeSet要好)。
HashSet还有一个子类LinkedHashSet,由于维护链表带来的额外开销,对于通常的添加、插入元素等操作,LinedHashSet比HashSet要慢一点,但由于有了链表,遍历LinkedHashSet会更快。
除此之外,注意Set的三个实现类都是线程不安全的,需要Collection工具类的synchronizedSortedSet方法来“包装” 该Set集合,此操作最好在创建时进行,如:
SortedSet s = Collections.synchronizedSortedSet(new TreeSet(…));

List集合

List 接口和ListIterator接口

List作为Collection接口的子接口,当然可以使用Collection接口里的全部方法,而且由于List是有序集合,因此List集合中添加了一些根据索引来操作集合元素的方法。
List判断两个对象相等是通过equals()方法比较返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A{
public boolean equals(Object obj){
return true;
}
}
import java.util.ArrayList;
import java.util.List;

public class ListTest2{
public static void main(String[] args){
List books = new ArrayList();
books.add(new String("aa"));
books.add(new String("bb"));
books.add(new String("cc"));
System.out.println(books);
books.remove(new A());
}
}

上面程序List调用该A对象的equals()方法依次与集合元素进行比较,对于A类对象的equals()方法,如果该equals()方法将某个集合元素作为参数时返回true,List将会删除该元素。由于上面程序改写了A类的equals()方法,该方法总是返回true,所以总是删除List集合的第一个元素。

Sort()、replaceAll()方法
函数式接口可使用Lambda表达式作为参数。如目标类型为Comparetor的Lambda表达式:
books.sort((o1,o2) ->((Stirng) o1).length() – ((Stirng)o2).length()); //字符串越长,字符串越大,List集合中的字符串会按从小到大的顺序排列。
目标类型为UnaryOperator的Lambda表达式:
books.replaceAll(ele -> ((String)ele).length()); //直接用集合元素(字符串)的长度作为新的集合元素。
与set只提供了iterator()方法不同,List还额外提供了iistIterator()方法,该方法返回了一个ListIterator对象,用于专门操作List。ListIterator增加了前向迭代功能,还能通过add()方法添加元素;而Iterator只能后向迭代,且只能删除元素。

ArrayList和Vector实现类

ArrayList和Vector作为List类的两个典型实现,完全支持前面介绍的List接口的全部功能。它们都是基于数组实现的List类。ArrayList和Vector对象使用initialCapacity参数设置该数组的长度,当向数组添加的元素超出了该数组长度时,它们的initialCapacity会自动增加。
ArrayList和Vector在用法上完全相同,只是Vector集合较老,当时java还没提供系统的集合框架,现在将Vector改为实现List接口,从而导致Vector里有一些功能重复的方法。
ArrayList类是线程不安全的。
Vector类是线程安全,性能较差的。由于Stack类继承了Vector,同样是线程安全性能较差。
Vector还提供各类一个Stack子类,后进先出“LIFO”。与java中的其它集合一样,进出栈都是Object,因此从栈中取出元素后必须进行类型转换。
ArrayDeque也是实现List类,既实现了List接口,又实现了Deque接口。

固定长度的List

JDK8中有双冒号的用法,就是把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下

1
2
3
String[] str = {“a”, “b”};
List<String> playsers = Arrays.asList(str);
players.forEach(players -> System.out.println(players + “,”));
1
2
//与上面等价
players.forEach(Sytem.out::println);

LinkedList比ArrayList功能更强大

Queue集合

Queue(/kju:/)用于模拟队列这种数据结构,先进先出“FIFO”。
Queue接口有一个PriorityQueue实现类。还有Deque接口(/dek/),代表双端队列,可以从两端添加、删除元素,因此Deque的实现类既可以当成队列使用,也可以当成栈使用。Deque提供两个实现类ArrayDeque类和LinkedList类。
常用方法:off()添加;peek()获取头部元素,不删除;poll()获取头部元素并删除

PriorityQueue实现类

它保存队列的元素是按大小进行重新排序,似乎违反了队列的FIFO原则。但输出可能并不是从小到大,是受到PriorityQueue的toString()方法的影响,调用poll()方法会发现元素从小到大移除队列。

Deque接口与ArrayDeque实现类

Deque接口的典型实现:ArrayDeque类。ArrayDeque既可以当栈使用,也可以当队列使用。
当程序中有栈这种数据结构时,推荐使用ArrayDeque,而不是Stack,因为Stack较古老,且性能较差。
作为栈使用时:ArrayDeque stack = new ArraDeque();方法push()、pop(),peek()访问第一个元素但不pop;
作为队列使用时:ArrayDeque queue = new ArrayDeque();方法offer()、poll(),peek()访问第一个元素但不poll。

LinkedList实现类

LinkedList类是List接口的实现类,意味着它是一个List集合,可以通过索引随机访问集合中元素;除此之外,它还实现了Deque接口,可以当成双端队列来使用:因此LinkList既可以当成使用,也可以当成队列使用。
book.length相当于book.size()
关于使用List集合的建议:
1)如果需要遍历List集合,对ArrayList、Vector集合,应该使用随机访问方法(get)来遍历集合元素,这样性能更好;对于LinkedList集合,则应该使用迭代器(Iterator)来遍历集合元素。
2)如果需要经常使用插入、删除等操作来改变 包含大量数据的List集合的大小,可考虑使用LinkedList集合。使用ArrayList和Vector可能需要经常分配内部数组的大小,性能可能较差。
3)如果由多个线程需要同时访问List集合中的元素,可以考虑使用Collections将集合包装成线程安全的集合。

Map集合

key和value之间存在单向一对一的关系。
Map包含一个keySet()方法,用于返回Map里所有key组成的Set集合(所有key没有顺序,key不能重复)。
Map的key集合Set集合里的元素的存储形式也很相似,Map子类和Set子类在名字上也相似,如Set接口下有HashSet、LinkedHashSet、SortedSet(接口)、TreeSet、EnumSet等子接口和实现类,而Map接口下则有HashMap、LinkedHashMap、SortedMap(接口)、TreeMap、EnumMap等子接口和实现类。Map的这些实现类和子接口中key集的存储形式和对应Set集合中元素的存储形式完全相同。
而如果把Map中的value放在一起来看的化,它们又很像一个List(元素之间可以重复,每个元素可以通过索引来查找,Map中是使用key索引来取元素)。

Java8改进的HashMap和Hashtable类

它们的关系与List的ArrayList和Vector的关系类似,Hashtable是一个古老的Map实现类。
Hashtable是一个线程安全的Map实现,但HashMap是线程不安全的实现,所以HashMap比Hashtable的性能要高一点;但如果多个线程访问同一个Map对象时,使用Hashtable实现类会更好。当最好不用Hashtable类,而是使用Collections工具类包HashMap变成线程安全的。
HashMap由于key值不能重复,所以最多只有一个key值为空,但可以有多个value值为空。
类似于HashSet,HashMap和Hashtable判断两个key相等的标准是:两个key通过equals()方法比较返回true,且key的hashCode值也相等。而判断两个value相等的标准是,两个对象通过equals()方法比较返回true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
*A类判断两个A对象相等的标准是count实例变量:
*只要两个A对象的count变量相等,
*则通过equals()方法比较返回true,且hashCode值也相等
*/
class A{
int count;
public A(int count){
this.count = count;
}
//根据count值判断两个对象是否相等
public boolean equals(Object obj){
if(obj == this){
return true;
}
if(obj != null && obj.getClass() == A.class){
A a = (A)obj;
return this.count == a.count;
}
return false;
}
//根据count计算hashCode值
public int hashCode(){
return this.count;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
*B对象可以和任何对象相等
*/
class B{
//重写equals()方法,使B对象与任何对象通过
//equals()方法比较返回true
public boolean equals(Object obj){
return true;
}
}
public class HashMapTest{
public static void main(String[] args){
HashMap hm = new HashMap();
hm.put(new A(60000), "aa");
hm.put(new A(87563), "bb");
hm.put(new A(1232), new B());
//不是按照添加的顺序输出的
System.out.println(hm + ",");
//hm.forEach((key,value) -> System.out.println(key + “--->” + value));
//由于B对象与任何value比较都为true,但输出却为false?
System.out.println(hm.containsValue("dd")); //true???
System.out.println(hm.containsKey(new A(87563))); //true
hm.remove(new A(1232)); //删除最后一个字符串
System.out.println(hm);
}
}
1
2
3
4
5
输出:
{com.Licht._08.HashMapTest.A@ea60=aa, com.Licht._08.HashMapTest.A@4d0=com.Licht._08.HashMapTest.B@15db9742, com.Licht._08.HashMapTest.A@1560b=bb},
false
true
{com.Licht._08.HashMapTest.A@ea60=aa, com.Licht._08.HashMapTest.A@1560b=bb}

与HashSet类似的是,如果使用可变对象作为HashMap、Hashtable的key,并且程序修改了作为key的可变对象,则有可能出现与HashSet类似的情形:程序无法准确访问到Map中被修改过的key。因此,与HashSet类似,尽量不要修改HashMap、Hashtable的key。

LinkedHashMap实现类

双向链表,LinkedHashMap可以避免HashMap、Hashtable对key-value值进行排序,使得迭代顺序与插入顺序保持一致。

使用Properties读取属性文件

Properties是Hashtable类的子类,属性文件中的“属性名=属性值”只能是字符串类型,所以key、value都是字符串类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
*Properties类的用法
*/
import java.util.Properties;
import java.io.FileOutputStream;
import java.io.FileInputStream;

public class PropertiesTest{
public static void main(String[] args)
throws Exception
{
Properties props1 = new Properties();
props1.setProperty("username", "A");
props1.setProperty("password", "123");
props1.store(new FileOutputStream("a.ini"),
"comment line");
Properties props2 = new Properties();
props2.setProperty("mail", "456@mail.com");
props2.load(new FileInputStream("a.ini"));
System.out.println(props2);
}
}

SortedMap接口和TreeMap实现类

正如Set接口派生出SortedSet子接口,SortedSet接口有一个TreeSet实现类,Map接口派生出SortedMap子接口,SortedMap接口有一个TreeMap实现类。
TreeMap就是一个红黑树数据结构,每个key-value值作为红黑树的一个节点。TreeMap也有两种排序方式:
自然排序:TreeMap的所有key必须实现Comparable接口,而且所有的key值都必须是同一类。
定制排序:创建TreeMap是,传入一个Comparator对象,该对象负责对该TreeMap中的所有key进行排序。
TreeMap中判断两个key相等的标准是:两个key通过compareTo()方法返回0。

WeakHashMap类

它与HashMap的用法基本类似,不同之处是WeakHashMap类对象的key所引用的对象如果没有被其它强引用变量所引用,则这些key所引用的对象会被垃圾回收,也可能会自动删除这些key所对应的key-value对。
匿名字符串(匿名对象即没有名字的对象),即直接new,没有其它引用。
字符串直接量。

IdentityHashMap类

这个Map实现类的实现机制与HashMap基本类似,不同之处在于,IdentityHashMap处理key时,仅当两个key值严格相等时(即key1 == key2),才认为两个key相等;
而对于HashMap类,当通过equals()方法比较返回true,且它们的hashCode值也相等时,才认为两个key值相等。
new String(“a”) 与 new String(“a”)通过 == 比较不等,因为匿名对象没有其它引用,而”a”与“a”严格相等,因为它们是字符串直接量。

EnumMap实现类

EnumMap是与枚举类一起使用的Map实现,它根据key的自然顺序(即枚举值在枚举类中的定义顺序)来维护key-value对的顺序。
key不能为null。
与创建普通Map不同的是,创建EnumMap类对象时必须指定一个枚举类,从而将EnumMap与指定枚举类联系起来。

Collections:操作集合的工具类

操作Set、List、Map等集合的工具类:Collections。对集合元素进行排序、查询、修改,将集合对象设置为不可变、实现同步控制等方法。

排序

查找、替换

同步控制

synchronized ()方法,将指定集合包装成线程同步的集合,解决多线程并发访问集合时的线程安全问题。

设置不可变集合

三种方法:

1
List unmodifiableList = Collections.emptyList(); // 创建一个空的、不可变List对象
1
Set umodifiableSet = Collections.singleton(A); // 创建一个只有一个元素,且不可改变的Set对象
1
2
3
4
5
//创建一个普通Map
Map scores = new HashMap();
scores.put(“语文”, 90);
scores.put(“数学”, 100);
Map unmodifiableMap = Collections.unmodifiableMap(scores); //返回普通Map对象对应的不可变版本